/*
Copyright 2016 S7connector members (github.com/s7connector)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.mt.roll.opcua.s7.my.s7connector.impl.nodave;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The Class TCPConnection.
 */
public final class TCPConnection extends S7Connection {

	/** The rack. */
	int rack;

	/** The slot. */
	int slot;
	private static final Logger logger = LoggerFactory.getLogger(TCPConnection.class);
	/**
	 * Instantiates a new TCP connection.
	 *
	 * @param ifa
	 *            the plc interface
	 * @param rack
	 *            the rack
	 * @param slot
	 *            the slot
	 */
	public TCPConnection(final PLCinterface ifa, final int rack, final int slot) {
		super(ifa);
		this.rack = rack;
		this.slot = slot;
		this.PDUstartIn = 7;
		//PDUstartOut = 7 是因为在Header部分之前 还有TPKT+COTP报文，并且这两个报文长度加起来一共7个字节。
		this.PDUstartOut = 7;
	}
	private static byte combineToByte(Integer i,Integer j){
		Integer cc = ((i << 4)+j);
		return cc.byteValue();
	}
	/**
	 * We have our own connectPLC(), but no disconnect() Open connection to a
	 * PLC. This assumes that dc is initialized by daveNewConnection and is not
	 * yet used. (or reused for the same PLC ?)
	 *
	 * @return the int
	 */
	public int connectPLC() {
		//COTP包 字节流（是上位机 和plc进行的第一次握手）
		final byte[] b4 = {    //b4.length = 18
				(byte) 0x11,	//（16进制）当前字节以后的字节数
				(byte) 0xE0,	//PDU type,连接请求【附录一】
				(byte) 0x00, (byte) 0x00,	//DST reference
				(byte) 0x00, (byte) 0x01,	//SRC reference
				(byte) 0x00,                //固定（Class     Extended formats    No explilcti flow control）

				(byte) 0xC1,				//parameter-code：dst-tsap 上位机
				(byte) 0x02,				//parameter-length
				(byte) 0x10,				//Source TSAP:   0x01->PG;  0x02->OP;  0x03->S7单边通讯(服务端模式);  0x10->S7双边通讯;;;     这里是0x01
				(byte) 0x00,				//机架(rack)(前4个bit位)+ cpu槽位(slot)(后4个bit位)  ,,  因为是上位机端  所以这里都是00(rack=0 slot=2)

				(byte) 0xC2,				//parameter-code：dst-tsap  PLC
				(byte) 0x02,				//parameter-length
				(byte) 0x03,				//Destination TSAP:   0x01->PG;  0x02->OP;  0x03->S7单边通讯(服务端模式);  0x10->S7双边通讯;;;     这里是0x01
				(byte) 0x01,				//机架(rack)(前4个bit位)+ cpu槽位(slot)(后4个bit位),,   西门子200smart、1200、1500默认 机架号=0,槽位号=1 ;;;;  西门子300、400默认 机架号=0,槽位号=2

				(byte) 0xC0, 				//parameter-code：tpdu-size
				(byte) 0x01,				//parameter-length
				(byte) 0x09					//TPDU size
		};

		//把 b4 这个数组从0开始位置 放到 this.msgOut 这个数组 第四个位置后面，放 b4.length 个长度
		//之所以目的位置4 是因为，前面4个字节是TPKT的内容(第一次握手)
		// msgOut 是这个socketChannel 往外写的 bytes（上位机 => plc 写） ；msgIn 是指这个socketChanne 外部往里写的bytes（上位机 <= plc 写）
		System.arraycopy(b4, 0, this.msgOut, 4, b4.length);
		//this.msgOut[17] = (byte) (this.rack + 1);   //这里写的不太对，这里应该是配置dst-tsap(plc)
		//this.msgOut[18] = (byte) this.slot;
		this.msgOut[18] = combineToByte(rack,slot);

		this.sendISOPacket(b4.length);
		this.readISOPacket();
		/*
		 * PDU p = new PDU(msgOut, 7); p.initHeader(1); p.addParam(b61);
		 * exchange(p); return (0);
		 */
		return this.negPDUlengthRequest();
	}

	/**
	 * 构建S7COMM 的COTP部分 3个字节（第二次握手）
	 *
	 * return
	 * 			1 代表失败
	 * 		    0 代表成功
	 * */
	@Override
	public int exchange(final PDU p1) {
		this.msgOut[4] = (byte) 0x02;  //当前字节以后的字节数（也就是下面两个字节）
		this.msgOut[5] = (byte) 0xf0;  //PDU Type
		this.msgOut[6] = (byte) 0x80;  //TPDU number (如果这个字节首位为1 代表就是最后一个传输单元了 如1000 0000 ;;  如果首字母不为1 就代表这个Request是比较长的要分多个unit传输，后7位就代表传输单元序号)
		int writeRes = this.sendISOPacket(3 + p1.hlen + p1.plen + p1.dlen);  //这里 TPKT 和 COTP字节一起发送的。。
		//这里读到的 ISO_Back_Packet 是暂存在msgIn 这暂存区的，后面可以直接拿里面的数据来构建 ISO_Back_Packet 的PDU
		int readRes = this.readISOPacket();
		if(writeRes!=0 && readRes!=0){
			return 0;
		}else {
			logger.info("exchange 出现了问题：① writeRes:"+writeRes+"② readRes:"+readRes);
			return 1    ;
		}
	}

	/**
	 * Read iso packet.
	 *
	 * @return the int
	 * 			0     ==>   不成功
	 * 		    -1    ==>   读到一个空的字节流 （这种情况很少）
	 * 			其他   ==>   成功
	 */
	protected int readISOPacket() {
		//read return 为0 就是异常
		int res = this.iface.read(this.msgIn, 0, 4);
		if (res == 4) {
			final int len = (0x100 * this.msgIn[2]) + this.msgIn[3]; //读取字节数
			res += this.iface.read(this.msgIn, 4, len);
		} else {
			return 0;
		}
		return res;
	}

//	public static void main(String[] args) {
//		byte[] s = new byte[2];
//		List<Byte> a = new ArrayList<>();
//		a.add(Byte.valueOf("1"));
//		a.add(Byte.valueOf("2"));
//		read(a);
//		System.out.println(a.get(0));
//		System.out.println(a.get(1));
//	}
//	public static int read(byte[] ss){
//		ss[0] = 1;
//		ss[1] = 1;
//		return 0;
//	}
//	public static int read(List<Byte> ss){
//		ss.set(0,Byte.valueOf("3"));
//		ss.set(1,Byte.valueOf("4"));
//		return 0;
//	}


	/**
	 * Send iso packet.
	 *
	 * @param size
	 *            the size
	 * @return the int
	 * 			1   ==> 成功
	 * 			0   ==> 不成功（出现异常了）
	 */
	protected int sendISOPacket(int size) {
		size += 4;
		//下面包装的 是TPKT 字节包（第一次握手）
		this.msgOut[0] = (byte) 0x03;            //Version ，默认版本3
		this.msgOut[1] = (byte) 0x0;			 //保留字节 ，默认0

		//这两个字节代表请求字节数，通常说都是固定的22，因为 TPKT字节数+COTP字节数 = 22
		this.msgOut[2] = (byte) (size / 0x100);//高位字节 除以8 取整 就放在高字节
		this.msgOut[3] = (byte) (size % 0x100);//低位字节 取余8 ，就放在低字节
		/*
		 * if (messageNumber == 0) { messageNumber = 1; msgOut[11] = (byte)
		 * ((messageNumber + 1) & 0xff); messageNumber++; messageNumber &= 0xff;
		 * //!! }
		 */
		//像这种 write 和read  都是 传的是指针，里面数据消费掉了，外部就没了
		int writeRes = this.iface.write(this.msgOut, 0, size);
		return writeRes;
	}


}
